package com.lumlord.common.spring;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.lumlord.common.domain.ApiMappingInfo;
import com.lumlord.common.domain.LumlordApi;
import com.lumlord.common.exception.BusinessException;
import com.lumlord.service.LumlordApiService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.lumlord.common.annotation.Api;
import com.lumlord.common.annotation.ControllerRequestMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 自定义路由
 * 
 * @author win 10
 *
 */
@Component
public class ApiControllerMapping extends RequestMappingHandlerMapping {
 /**
	 * {@inheritDoc}
	 * <p>
	 * Expects a handler to have either a type-level @{@link Controller} annotation
	 * or a type-level @{@link RequestMapping} annotation.
	 */
	@Override
	protected boolean isHandler(Class<?> beanType) {
		return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class)
				|| AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class))
				|| AnnotatedElementUtils.hasAnnotation(beanType, Api.class);
	}

	/**
	 * Uses method and type-level @{@link RequestMapping} annotations to create the
	 * RequestMappingInfo.
	 * 
	 * @return the created RequestMappingInfo, or {@code null} if the method does
	 *         not have a {@code @RequestMapping} annotation.
	 * @see #getCustomMethodCondition(Method)
	 * @see #getCustomTypeCondition(Class)
	 */
	
	
	 
	
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType, String classNameSpace) {
		RequestMappingInfo info = createRequestMappingInfo(method,classNameSpace);
		if (info != null) {
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType,classNameSpace);
			if (typeInfo != null) {
				info = typeInfo.combine(info);
			}
		}
		

		return info;
	}
	
	
	@Nullable
	protected RequestMappingInfo getApiForMethod(Method method, Class<?> handlerType, String classNameSpace) {
		RequestMappingInfo info = createApiInfo(method,classNameSpace);
		if (info != null) {
			RequestMappingInfo typeInfo = createApiInfo(handlerType,classNameSpace);
			if (typeInfo != null) {
				info = typeInfo.combine(info);
			}
		}
		

		return info;
	}
	
	
	@Nullable
	private RequestMappingInfo createApiInfo(AnnotatedElement element,String nameSpace) {
		Api api = AnnotatedElementUtils.findMergedAnnotation(element, Api.class);
		RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element)
				: getCustomMethodCondition((Method) element));
		 
		return (api != null ? createApiInfo(api, condition,nameSpace) : null);
	}
	
	
	protected RequestMappingInfo createApiInfo(
			Api api, @Nullable RequestCondition<?> customCondition,String nameSpace) {
		String[] urls = resolveEmbeddedValuesInPatterns(api.path());
		if(null != nameSpace) {
			for(int index=0;index<urls.length;index++) {
				urls[index] = nameSpace +"/"+urls[index];
				if(urls[index].indexOf("index")!=-1) {
					System.out.println();
				}
			}
		}
		
		RequestMappingInfo.Builder builder = RequestMappingInfo
				.paths(urls)
				.methods(api.method())
				.params(api.params())
				.headers(new String[0])
				.consumes(new String[0])
				.produces("application/json")
				.mappingName("");
		if (customCondition != null) {
			builder.customCondition(customCondition);
		}
		return builder.options(this.config).build();
	}
	
	/**
	 * Delegates to
	 * {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
	 * supplying the appropriate custom {@link RequestCondition} depending on
	 * whether the supplied {@code annotatedElement} is a class or method.
	 * 
	 * @see #getCustomTypeCondition(Class)
	 * @see #getCustomMethodCondition(Method)
	 */
	@Nullable
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element,String nameSpace) {
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
		RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element)
				: getCustomMethodCondition((Method) element));
		 
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition,nameSpace) : null);
	}
	protected RequestMappingInfo createRequestMappingInfo(
			RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition,String nameSpace) {
		String[] urls = resolveEmbeddedValuesInPatterns(requestMapping.path());
		if(null != nameSpace) {
			for(int index=0;index<urls.length;index++) {
				urls[index] = nameSpace +"/"+urls[index];
				if(urls[index].indexOf("index")!=-1) {
					System.out.println();
				}
			}
		}
		
		
		RequestMappingInfo.Builder builder = RequestMappingInfo
				.paths(urls)
				.methods(requestMapping.method())
				.params(requestMapping.params())
				.headers(requestMapping.headers())
				.consumes(requestMapping.consumes())
				.produces(requestMapping.produces())
				.mappingName(requestMapping.name());
		if (customCondition != null) {
			builder.customCondition(customCondition);
		}
		return builder.options(this.config).build();
	}
	 
	protected boolean isControllerRequestMapping(Class<?> beanType) {
		return AnnotatedElementUtils.hasAnnotation(beanType, ControllerRequestMapping.class);
	}
	
	
	protected boolean isApiMapping(Class<?> beanType) {
		return AnnotatedElementUtils.hasAnnotation(beanType, Api.class);
	}
	
	
	@Override
	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler)
				: handler.getClass());
		Api annotationApi = null;
		String clazzName = "";
		boolean isApi = false;
		if (handlerType != null) {//修改注解 ControllerRequestMapping path
			if (isControllerRequestMapping(handlerType)) {
				String className = handlerType.getName();
				String nameSpace = className.substring(className.lastIndexOf(".") + 1);
				
				if (nameSpace.indexOf("Controller") == -1) {
					System.out.println(className + " 沒有 Controller !");
				}

				annotationApi = AnnotatedElementUtils.findMergedAnnotation(handlerType, Api.class);//handlerType.getAnnotation(Api.class);
				ControllerRequestMapping mapping =  AnnotatedElementUtils.findMergedAnnotation(handlerType, ControllerRequestMapping.class);
				
				if(null==annotationApi) {
					System.out.println(className);
				}
				
				if("".equals(annotationApi.NameSpace())) {
					String myNameSpace = nameSpace.substring(0,nameSpace.lastIndexOf("Controller"));
					clazzName = ("" + myNameSpace.charAt(0)).toLowerCase() + myNameSpace.substring(1);
					clazzName = mapping.path()+"/"+clazzName;
				}else {
						clazzName = mapping.path()+"/"+annotationApi.NameSpace();
				}
				isApi = true;
			}
			final  Api finallAnnotationApi = annotationApi;
			final String clazzNameSpace = clazzName;
			final boolean api = isApi;
			final Class<?> userType = ClassUtils.getUserClass(handlerType);
			Map<Method, Object> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<Object>) method -> {
						try {
							if(api) {
								put(method,handlerType,finallAnnotationApi);
								//registerApi(method,handlerType);
								return getApiForMethod(method, handlerType, clazzNameSpace);
							}else
								return getMappingForMethod(method, userType,clazzNameSpace);
						
						
						} catch (Throwable ex) {
							throw new IllegalStateException(
									"Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
						}
					});
			if (logger.isDebugEnabled()) {
				logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
			}
			for (Map.Entry<Method, Object> entry : methods.entrySet()) {
				Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
				Object mapping = entry.getValue();
				RequestMappingInfo requestMapping = (RequestMappingInfo) mapping;

				registerHandlerMethod(handler, invocableMethod, requestMapping);
			}
		}
	}

	private void put(Method method,Class clazz,Api api){
		ApiMappingInfo info = new ApiMappingInfo();
		info.setApi(api);
		info.setClazz(clazz);
		info.setMethod(method);
		apis.add(info);
	}


	private void registerApi(Method method, Class clazz) {
		if (!openregister) {
			return;
		}


		/*LumlordApi api = new LumlordApi();
		api.setMenuUrl("API");
		api.setMethodName(method.toString());
		api.setClazz(clazz.toString());

		Api clazzApi = AnnotatedElementUtils.findMergedAnnotation(clazz, Api.class);
		if (null != clazzApi) {
			api.setModeName(clazzApi.moduleName());
		}

 		Api methodApi = method.getAnnotation(Api.class);
		if(null == methodApi){
			return;
		}
		api.setName(methodApi.actionName());
		ControllerRequestMapping controllerRequestMapping = AnnotatedElementUtils
				.findMergedAnnotation(clazz, ControllerRequestMapping.class);
		StringBuffer methodName = new StringBuffer();
		if(methodApi.method().length>0){
			for(RequestMethod requestMethod  : methodApi.method()){
				methodName.append(methodApi.method()[0].name()+":");
			}
			if(methodApi.toString().endsWith(":")){
				methodName.deleteCharAt(methodName.length() - 1);
			}
		}else{
			return;
		}

		if(controllerRequestMapping!=null){
			if("".equals(clazzApi.NameSpace())) {
				api.setAction(methodName + " " + serverName +"/"+controllerRequestMapping.path()+"/"+clazzApi.NameSpace()+"/"+methodApi.path()[0]);
			}else {
				String className = clazz.getName();
				String nameSpace = className.substring(className.lastIndexOf(".") + 1);
				api.setAction(methodName+ " " + serverName +"/"+controllerRequestMapping.path()+"/"+nameSpace+"/"+methodApi.path()[0]);
			}

		}


		api.setDesigner(designer);
		api.setServerName(serverName);
		api.setVueCode(serverName + ":" + method.toString());

		// api.setRemark(remark);

		try {
			service.registerApi(api);
		} catch (BusinessException e) {
			e.printStackTrace();
		}
*/
	}


	public List<ApiMappingInfo> getApis() {
		return apis;
	}

	private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
	private final List<ApiMappingInfo> apis = new ArrayList<ApiMappingInfo>(100);
	@Value("${lumlord.designer}")
	private String designer;
	@Value("${server.servlet.context-path}")
	private String serverName;
	/**
	 * 是否开启注册
	 */
	@Value("${lumlord.openregister}")
	private boolean openregister;
	@Resource
	private LumlordApiService service;
}